%# -*- c -*-
#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include "internal.h"
#include "rubyparser.h"
#define YYSTYPE_IS_DECLARED
#include "parse.h"
#include "internal/parse.h"
#include "internal/ruby_parser.h"
#include "node.h"
#include "eventids1.h"
#include "eventids2.h"
#include "ripper_init.h"

#define STR_NEW2(ptr) rb_enc_str_new((ptr),strlen(ptr),rb_ruby_parser_enc(p))
#define RIPPER_VERSION "0.1.0"

ID id_warn, id_warning, id_gets, id_assoc;

enum lex_type {
    lex_type_str,
    lex_type_io,
    lex_type_generic,
};

struct ripper {
    rb_parser_t *p;
    enum lex_type type;
    union {
       struct lex_pointer_string ptr_str;
       VALUE val;
    } data;
};

static void
ripper_parser_mark2(void *ptr)
{
    struct ripper *r = (struct ripper*)ptr;
    if (r->p) {
        ripper_parser_mark(r->p);

        switch (r->type) {
          case lex_type_str:
            rb_gc_mark(r->data.ptr_str.str);
            break;
          case lex_type_io:
            rb_gc_mark(r->data.val);
            break;
          case lex_type_generic:
            rb_gc_mark(r->data.val);
            break;
        }
    }
}

static void
ripper_parser_free2(void *ptr)
{
    struct ripper *r = (struct ripper*)ptr;
    if (r->p) ripper_parser_free(r->p);
    xfree(r);
}

static size_t
ripper_parser_memsize2(const void *ptr)
{
    struct ripper *r = (struct ripper*)ptr;
    return (r->p) ? ripper_parser_memsize(r->p) : 0;
}

static const rb_data_type_t parser_data_type = {
    "ripper",
    {
        ripper_parser_mark2,
        ripper_parser_free2,
        ripper_parser_memsize2,
    },
    0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};

static rb_parser_string_t *
ripper_lex_get_generic(struct parser_params *p, rb_parser_input_data input, int line_count)
{
    VALUE src = (VALUE)input;
    VALUE line = rb_funcallv_public(src, id_gets, 0, 0);
    if (NIL_P(line)) return 0;
    if (!RB_TYPE_P(line, T_STRING)) {
        rb_raise(rb_eTypeError,
                 "gets returned %"PRIsVALUE" (expected String or nil)",
                 rb_obj_class(line));
    }
    return rb_str_to_parser_string(p, line);
}

void
ripper_compile_error(struct parser_params *p, const char *fmt, ...)
{
    VALUE str;
    va_list args;

    va_start(args, fmt);
    str = rb_vsprintf(fmt, args);
    va_end(args);
    rb_funcall(ripper_value(p), rb_intern("compile_error"), 1, str);
    ripper_error(p);
}

static rb_parser_string_t *
ripper_lex_io_get(struct parser_params *p, rb_parser_input_data input, int line_count)
{
    VALUE src = (VALUE)input;
    VALUE line = rb_io_gets(src);
    if (NIL_P(line)) return 0;
    return rb_str_to_parser_string(p, line);
}

static rb_parser_string_t *
ripper_lex_get_str(struct parser_params *p, rb_parser_input_data input, int line_count)
{
    return rb_parser_lex_get_str(p, (struct lex_pointer_string *)input);
}

static VALUE
ripper_s_allocate(VALUE klass)
{
    struct ripper *r;

    VALUE self = TypedData_Make_Struct(klass, struct ripper,
                                       &parser_data_type, r);

#ifdef UNIVERSAL_PARSER
    const rb_parser_config_t *config = rb_ruby_parser_config();
    r->p = rb_ripper_parser_params_allocate(config);
#else
    r->p = rb_ruby_ripper_parser_allocate();
#endif
    rb_ruby_parser_set_value(r->p, self);
    return self;
}

static struct parser_params *
ripper_parser_params(VALUE self, bool initialized)
{
    struct ripper *r;
    struct parser_params *p;

    TypedData_Get_Struct(self, struct ripper, &parser_data_type, r);
    p = r->p;
    if (initialized && !rb_ruby_ripper_initialized_p(p)) {
        rb_raise(rb_eArgError, "method called for uninitialized object");
    }
    return p;
}

/*
 *  call-seq:
 *    ripper.error?   -> Boolean
 *
 *  Return true if parsed source has errors.
 */
static VALUE
ripper_error_p(VALUE vparser)
{
    struct parser_params *p = ripper_parser_params(vparser, false);

    return RBOOL(rb_ruby_parser_error_p(p));
}

/*
 *  call-seq:
 *    ripper.end_seen?   -> Boolean
 *
 *  Return true if parsed source ended by +\_\_END\_\_+.
 */
static VALUE
ripper_parser_end_seen_p(VALUE vparser)
{
    struct parser_params *p = ripper_parser_params(vparser, false);

    return RBOOL(rb_ruby_parser_end_seen_p(p));
}

/*
 *  call-seq:
 *    ripper.encoding   -> encoding
 *
 *  Return encoding of the source.
 */
static VALUE
ripper_parser_encoding(VALUE vparser)
{
    struct parser_params *p = ripper_parser_params(vparser, false);

    return rb_enc_from_encoding(rb_ruby_parser_encoding(p));
}

/*
 *  call-seq:
 *    ripper.yydebug   -> true or false
 *
 *  Get yydebug.
 */
static VALUE
ripper_parser_get_yydebug(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, false);

    return RBOOL(rb_ruby_parser_get_yydebug(p));
}

/*
 *  call-seq:
 *    ripper.yydebug = flag
 *
 *  Set yydebug.
 */
static VALUE
ripper_parser_set_yydebug(VALUE self, VALUE flag)
{
    struct parser_params *p = ripper_parser_params(self, false);

    rb_ruby_parser_set_yydebug(p, RTEST(flag));
    return flag;
}

/*
 *  call-seq:
 *    ripper.debug_output   -> obj
 *
 *  Get debug output.
 */
static VALUE
ripper_parser_get_debug_output(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, false);

    return rb_ruby_parser_debug_output(p);
}

/*
 *  call-seq:
 *    ripper.debug_output = obj
 *
 *  Set debug output.
 */
static VALUE
ripper_parser_set_debug_output(VALUE self, VALUE output)
{
    struct parser_params *p = ripper_parser_params(self, false);

    rb_ruby_parser_set_debug_output(p, output);
    return output;
}

#ifdef UNIVERSAL_PARSER
struct dedent_string_arg {
    struct parser_params *p;
    VALUE input;
    VALUE width;
};

static VALUE
parser_dedent_string0(VALUE a)
{
    struct dedent_string_arg *arg = (void *)a;
    int wid, col;

    StringValue(arg->input);
    wid = NUM2UINT(arg->width);
    col = rb_ruby_ripper_dedent_string(arg->p, arg->input, wid);
    return INT2NUM(col);
}

static VALUE
parser_free(VALUE a)
{
    struct parser_params *p = (void *)a;

    rb_ruby_parser_free(p);
    return Qnil;
}
#endif

/*
 *  call-seq:
 *    Ripper.dedent_string(input, width)   -> Integer
 *
 *  USE OF RIPPER LIBRARY ONLY.
 *
 *  Strips up to +width+ leading whitespaces from +input+,
 *  and returns the stripped column width.
 */
#ifdef UNIVERSAL_PARSER
static VALUE
parser_dedent_string(VALUE self, VALUE input, VALUE width)
{
    struct parser_params *p;
    struct dedent_string_arg args;

    p = rb_parser_params_new();

    args.p = p;
    args.input = input;
    args.width = width;
    return rb_ensure(parser_dedent_string0, (VALUE)&args, parser_free, (VALUE)p);
}
#else
static VALUE
parser_dedent_string(VALUE self, VALUE input, VALUE width)
{
    int wid, col;

    StringValue(input);
    wid = NUM2UINT(width);
    col = rb_ruby_ripper_dedent_string(0, input, wid);
    return INT2NUM(col);
}
#endif

/*
 *  call-seq:
 *    Ripper.new(src, filename="(ripper)", lineno=1) -> ripper
 *
 *  Create a new Ripper object.
 *  _src_ must be a String, an IO, or an Object which has #gets method.
 *
 *  This method does not starts parsing.
 *  See also Ripper#parse and Ripper.parse.
 */
static VALUE
ripper_initialize(int argc, VALUE *argv, VALUE self)
{
    struct ripper *r;
    struct parser_params *p;
    VALUE src, fname, lineno;
    rb_parser_lex_gets_func *gets;
    VALUE sourcefile_string;
    const char *sourcefile;
    int sourceline;
    rb_parser_input_data input;

    p = ripper_parser_params(self, false);
    TypedData_Get_Struct(self, struct ripper, &parser_data_type, r);
    rb_scan_args(argc, argv, "12", &src, &fname, &lineno);
    if (RB_TYPE_P(src, T_FILE)) {
        gets = ripper_lex_io_get;
        r->type = lex_type_io;
        r->data.val = src;
        input = (rb_parser_input_data)src;
    }
    else if (rb_respond_to(src, id_gets)) {
        gets = ripper_lex_get_generic;
        r->type = lex_type_generic;
        r->data.val = src;
        input = (rb_parser_input_data)src;
    }
    else {
        StringValue(src);
        gets = ripper_lex_get_str;
        r->type = lex_type_str;
        r->data.ptr_str.str = src;
        r->data.ptr_str.ptr = 0;
        input = (rb_parser_input_data)&r->data.ptr_str;
    }
    if (NIL_P(fname)) {
        fname = STR_NEW2("(ripper)");
        OBJ_FREEZE(fname);
    }
    else {
        StringValueCStr(fname);
        fname = rb_str_new_frozen(fname);
    }
    rb_ruby_ripper_parser_initialize(p);

    sourcefile_string = fname;
    sourcefile = RSTRING_PTR(fname);
    sourceline = NIL_P(lineno) ? 0 : NUM2INT(lineno) - 1;

    rb_ruby_parser_ripper_initialize(p, gets, input, sourcefile_string, sourcefile, sourceline);

    return Qnil;
}

static VALUE
ripper_parse0(VALUE vparser)
{
    struct parser_params *p = ripper_parser_params(vparser, false);

    rb_ruby_ripper_parse0(p);
    return rb_ruby_parser_result(p);
}

static VALUE
ripper_ensure(VALUE vparser)
{
    struct parser_params *p = ripper_parser_params(vparser, false);

    rb_ruby_parser_set_parsing_thread(p, Qnil);
    return Qnil;
}

/*
 *  call-seq:
 *    ripper.parse
 *
 *  Start parsing and returns the value of the root action.
 */
static VALUE
ripper_parse(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, true);
    VALUE result;

    if (!NIL_P(rb_ruby_parser_parsing_thread(p))) {
        if (rb_ruby_parser_parsing_thread(p) == rb_thread_current())
            rb_raise(rb_eArgError, "Ripper#parse is not reentrant");
        else
            rb_raise(rb_eArgError, "Ripper#parse is not multithread-safe");
    }
    rb_ruby_parser_set_parsing_thread(p, rb_thread_current());
    result = rb_ensure(ripper_parse0, self, ripper_ensure, self);
    RB_GC_GUARD(self);

    return result;
}

/*
 *  call-seq:
 *    ripper.column   -> Integer
 *
 *  Return column number of current parsing line.
 *  This number starts from 0.
 */
static VALUE
ripper_column(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, true);
    long col;

    if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
    col = rb_ruby_ripper_column(p);
    return LONG2NUM(col);
}

/*
 *  call-seq:
 *    ripper.filename   -> String
 *
 *  Return current parsing filename.
 */
static VALUE
ripper_filename(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, true);

    return rb_ruby_parser_ruby_sourcefile_string(p);
}

/*
 *  call-seq:
 *    ripper.lineno   -> Integer
 *
 *  Return line number of current parsing line.
 *  This number starts from 1.
 */
static VALUE
ripper_lineno(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, true);

    if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
    return INT2NUM(rb_ruby_parser_ruby_sourceline(p));
}

/*
 *  call-seq:
 *    ripper.state   -> Integer
 *
 *  Return scanner state of current token.
 */
static VALUE
ripper_state(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, true);

    if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
    return INT2NUM(rb_ruby_parser_lex_state(p));
}

/*
 *  call-seq:
 *    ripper.token   -> String
 *
 *  Return the current token string.
 */
static VALUE
ripper_token(VALUE self)
{
    struct parser_params *p = ripper_parser_params(self, true);
    long pos, len;
    VALUE str;

    if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
    pos = rb_ruby_ripper_column(p);
    len = rb_ruby_ripper_token_len(p);
    str = rb_str_new_parser_string(rb_ruby_ripper_lex_lastline(p));
    return rb_str_subseq(str, pos, len);
}

#ifdef RIPPER_DEBUG
/* :nodoc: */
static VALUE
ripper_assert_Qundef(VALUE self, VALUE obj, VALUE msg)
{
    StringValue(msg);
    if (UNDEF_P(obj)) {
        rb_raise(rb_eArgError, "%"PRIsVALUE, msg);
    }
    return Qnil;
}

/* :nodoc: */
static VALUE
ripper_raw_value(VALUE self, VALUE obj)
{
    return ULONG2NUM(obj);
}

/* :nodoc: */
static VALUE
ripper_validate_object(VALUE self, VALUE x)
{
    if (x == Qfalse) return x;
    if (x == Qtrue) return x;
    if (NIL_P(x)) return x;
    if (UNDEF_P(x))
        rb_raise(rb_eArgError, "Qundef given");
    if (FIXNUM_P(x)) return x;
    if (SYMBOL_P(x)) return x;
    switch (BUILTIN_TYPE(x)) {
      case T_STRING:
      case T_OBJECT:
      case T_ARRAY:
      case T_BIGNUM:
      case T_FLOAT:
      case T_COMPLEX:
      case T_RATIONAL:
        break;
      default:
        rb_raise(rb_eArgError, "wrong type of ruby object: %p (%s)",
                 (void *)x, rb_obj_classname(x));
    }
    if (!RBASIC_CLASS(x)) {
        rb_raise(rb_eArgError, "hidden ruby object: %p (%s)",
                 (void *)x, rb_builtin_type_name(TYPE(x)));
    }
    return x;
}
#endif

#ifdef UNIVERSAL_PARSER
struct lex_state_name_arg {
    struct parser_params *p;
    VALUE state;
};

static VALUE
lex_state_name0(VALUE a)
{
    struct lex_state_name_arg *arg = (void *)a;

    return rb_ruby_ripper_lex_state_name(arg->p, NUM2INT(arg->state));
}
#endif

/*
 *  call-seq:
 *    Ripper.lex_state_name(integer)   -> string
 *
 *  Returns a string representation of lex_state.
 */
#ifdef UNIVERSAL_PARSER
static VALUE
ripper_lex_state_name(VALUE self, VALUE state)
{
    struct parser_params *p;
    struct lex_state_name_arg args;

    p = rb_parser_params_new();

    args.p = p;
    args.state = state;

    return rb_ensure(lex_state_name0, (VALUE)&args, parser_free, (VALUE)p);
}
#else
static VALUE
ripper_lex_state_name(VALUE self, VALUE state)
{
    return rb_ruby_ripper_lex_state_name(0, NUM2INT(state));
}
#endif

void
Init_ripper(void)
{
    ripper_init_eventids1();
    ripper_init_eventids2();
    id_warn = rb_intern_const("warn");
    id_warning = rb_intern_const("warning");
    id_gets = rb_intern_const("gets");
    id_assoc = rb_intern_const("=>");

    InitVM(ripper);
}

void
InitVM_ripper(void)
{
    VALUE Ripper;

    Ripper = rb_define_class("Ripper", rb_cObject);
    /* version of Ripper */
    rb_define_const(Ripper, "Version", rb_usascii_str_new2(RIPPER_VERSION));
    rb_define_alloc_func(Ripper, ripper_s_allocate);
    rb_define_method(Ripper, "initialize", ripper_initialize, -1);
    rb_define_method(Ripper, "parse", ripper_parse, 0);
    rb_define_method(Ripper, "column", ripper_column, 0);
    rb_define_method(Ripper, "filename", ripper_filename, 0);
    rb_define_method(Ripper, "lineno", ripper_lineno, 0);
    rb_define_method(Ripper, "state", ripper_state, 0);
    rb_define_method(Ripper, "token", ripper_token, 0);
    rb_define_method(Ripper, "end_seen?", ripper_parser_end_seen_p, 0);
    rb_define_method(Ripper, "encoding", ripper_parser_encoding, 0);
    rb_define_method(Ripper, "yydebug", ripper_parser_get_yydebug, 0);
    rb_define_method(Ripper, "yydebug=", ripper_parser_set_yydebug, 1);
    rb_define_method(Ripper, "debug_output", ripper_parser_get_debug_output, 0);
    rb_define_method(Ripper, "debug_output=", ripper_parser_set_debug_output, 1);
    rb_define_method(Ripper, "error?", ripper_error_p, 0);
#ifdef RIPPER_DEBUG
    rb_define_method(Ripper, "assert_Qundef", ripper_assert_Qundef, 2);
    rb_define_method(Ripper, "rawVALUE", ripper_raw_value, 1);
    rb_define_method(Ripper, "validate_object", ripper_validate_object, 1);
#endif

    rb_define_singleton_method(Ripper, "dedent_string", parser_dedent_string, 2);
    rb_define_private_method(Ripper, "dedent_string", parser_dedent_string, 2);

    rb_define_singleton_method(Ripper, "lex_state_name", ripper_lex_state_name, 1);

<% @exprs.each do |expr, desc| -%>
    /* <%=desc%> */
    rb_define_const(Ripper, "<%=expr%>", INT2NUM(<%=expr%>));
<% end %>
    ripper_init_eventids1_table(Ripper);
    ripper_init_eventids2_table(Ripper);

# if 0
    /* Hack to let RDoc document SCRIPT_LINES__ */

    /*
     * When a Hash is assigned to +SCRIPT_LINES__+ the contents of files loaded
     * after the assignment will be added as an Array of lines with the file
     * name as the key.
     */
    rb_define_global_const("SCRIPT_LINES__", Qnil);
#endif
}
